﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Globalization;

namespace DiagnosisMaker
{
    public class DiagnosisMaker
    {
        private string defaultClass;
        
        static void Main(string[] args)
        {
            
            List<string> filestemNames = new List<string>();
            filestemNames.Add("outlook");
            filestemNames.Add("temperature");
            filestemNames.Add("humidity");
            filestemNames.Add("windy");

            // Testes comparativos com o C4.5
            //List<string> filestemData = new List<string>();
            //filestemData.Add("37.0");
            //filestemData.Add("yes");
            //filestemData.Add("no");
            //filestemData.Add("yes");
          
            //List<string> filestemData = new List<string>();
            //filestemData.Add("37.0");
            //filestemData.Add("no");
            //filestemData.Add("no");
            //filestemData.Add("yes");           

            //List<string> filestemData = new List<string>();
            //filestemData.Add("37.0");
            //filestemData.Add("yes");
            //filestemData.Add("no");
            //filestemData.Add("no");          
            
            //List<string> filestemData = new List<string>();
            //filestemData.Add("39.0");
            //filestemData.Add("no");
            //filestemData.Add("yes");
            //filestemData.Add("no");       
            
            //List<string> filestemData = new List<string>();
            //filestemData.Add("sunny");
            //filestemData.Add("50");
            //filestemData.Add("50");
            //filestemData.Add("false");             

            //List<string> filestemData = new List<string>();
            //filestemData.Add("overcast");
            //filestemData.Add("50");
            //filestemData.Add("50");
            //filestemData.Add("false");             

            //List<string> filestemData = new List<string>();
            //filestemData.Add("rain");
            //filestemData.Add("50");
            //filestemData.Add("50");
            //filestemData.Add("false");             

            //List<string> filestemData = new List<string>();
            //filestemData.Add("sunny");
            //filestemData.Add("80");
            //filestemData.Add("80");
            //filestemData.Add("false");             

            List<string> filestemData = new List<string>();
            filestemData.Add("rain");
            filestemData.Add("80");
            filestemData.Add("80");
            filestemData.Add("true");    

            DiagnosisMaker dm = new DiagnosisMaker();
            string result = dm.returnDiagnosis("C:\\Users\\SAMUEL\\Desktop\\Data","diagnosis",filestemNames, filestemData);

            Console.WriteLine(result);
            Console.ReadLine();
        }
        
        public string returnDiagnosis(string filestemPath, string filestem, List<string> filestemNames, List<string> filestemData)
        {

            // Executes c4.5.exe for generating the decision tree
            Process c45Process = new Process();
            c45Process.StartInfo.FileName = @filestemPath + "\\c4.5.exe";
            c45Process.StartInfo.UseShellExecute = false;
            c45Process.StartInfo.CreateNoWindow = true;
            c45Process.StartInfo.RedirectStandardInput = true;
            c45Process.StartInfo.RedirectStandardOutput = true;
            c45Process.StartInfo.Arguments = " -f " + filestemPath + "\\" + filestem;
            c45Process.Start();
            c45Process.WaitForExit();

            // Executes c4.5rules.exe for generating the rules from previous decision tree generated 
            // PS: get the output generated from the c4.5rules.exe to process the result after
            string c45Rules = "";
            Process c45RulesProcess = new Process();            
            c45RulesProcess.StartInfo.FileName = @filestemPath + "\\c4.5rules.exe";
            c45RulesProcess.StartInfo.UseShellExecute = false;
            c45RulesProcess.StartInfo.RedirectStandardInput = true;
            c45RulesProcess.StartInfo.RedirectStandardOutput = true;
            c45RulesProcess.StartInfo.Arguments = " -f " + filestemPath + "\\" + filestem;

            c45RulesProcess.Start();
            c45RulesProcess.WaitForExit(); 

            StreamReader sOut = c45RulesProcess.StandardOutput;

            c45Rules = sOut.ReadToEnd();

            sOut.Close();
            c45RulesProcess.Close();

            //Console.WriteLine(c45Rules);
            //Console.ReadLine();

            List<Rule> rulesGenerated = makeRules(c45Rules);

            return interpretRules(rulesGenerated,filestemNames,filestemData);

        }


        private List<Rule> makeRules(string c45Rules)
        {
            List<Rule> rules = new List<Rule>();
            Rule currentRule;
            Condition currentCondition;
            Boolean endOfConditions = false;            
            Boolean endOfRules = false;

            //trims the output to get only the rules
            c45Rules = c45Rules.Substring(c45Rules.IndexOf("Rule "), c45Rules.Length - c45Rules.IndexOf("Rule "));
            c45Rules = c45Rules.Substring(0, c45Rules.IndexOf("Evaluation "));

            
            while (endOfRules == false){
                currentRule = new Rule();
                currentRule.Id = int.Parse(c45Rules.Substring(c45Rules.IndexOf(":") - 1, 1));
                c45Rules = c45Rules.Substring(c45Rules.IndexOf(":") + 3 , c45Rules.Length - (c45Rules.IndexOf(":") + 3));

                string ruleConditions;

                if (c45Rules.IndexOf("Rule ") == -1){
                    ruleConditions = c45Rules.Substring(0, c45Rules.IndexOf("Default "));
                    endOfRules = true;
                }else{
                    ruleConditions = c45Rules.Substring(0,c45Rules.IndexOf("Rule "));
                }
                currentRule.Conditions = new List<Condition>();

                while (endOfConditions == false){
                    ruleConditions = ruleConditions.Substring(ruleConditions.IndexOf("\t"), ruleConditions.Length - ruleConditions.IndexOf("\t")).Trim();
                    if (ruleConditions.IndexOf("->") == 0){
                        ruleConditions = ruleConditions.Substring(ruleConditions.IndexOf(" "), ruleConditions.Length - ruleConditions.IndexOf(" ")).TrimStart();
                        ruleConditions = ruleConditions.Substring(ruleConditions.IndexOf(" "), ruleConditions.Length - ruleConditions.IndexOf(" ")).TrimStart();
                        currentRule.Result = ruleConditions.Substring(0, ruleConditions.IndexOf("[") - 2);
                        currentRule.Accuracy = float.Parse(ruleConditions.Substring(ruleConditions.IndexOf("[") + 1, ruleConditions.IndexOf("%") - (ruleConditions.IndexOf("[") + 1)),CultureInfo.InvariantCulture);
                        endOfConditions = true;
                    }else{
                        currentCondition = new Condition();
                        currentCondition.Attribute = ruleConditions.Substring(0, ruleConditions.IndexOf(" ")).Trim();
                        ruleConditions = ruleConditions.Substring(ruleConditions.IndexOf(" "), ruleConditions.Length - ruleConditions.IndexOf(" ")).TrimStart();
                        switch (ruleConditions.Substring(0, ruleConditions.IndexOf(" ")).Trim()){
                            case "=":
                                currentCondition.Operation = operations.equals;
                                break;
                            case "<":
                                currentCondition.Operation = operations.less;
                                break;
                            case ">":
                                currentCondition.Operation = operations.more;
                                break;
                            case "<=":
                                currentCondition.Operation = operations.lessEqual;
                                break;
                            case ">=":
                                currentCondition.Operation = operations.moreEqual;
                                break;
                        }
                        ruleConditions = ruleConditions.Substring(ruleConditions.IndexOf(" "), ruleConditions.Length - ruleConditions.IndexOf(" ")).TrimStart();
                        currentCondition.Value = ruleConditions.Substring(0, ruleConditions.IndexOf("\n")).Trim();
                        currentRule.Conditions.Add(currentCondition);
                        ruleConditions = ruleConditions.Substring(ruleConditions.IndexOf("\n"), ruleConditions.Length - ruleConditions.IndexOf("\n"));
                    }                
                }

                if (endOfRules){
                    this.defaultClass = c45Rules.Substring(c45Rules.IndexOf(":") + 1, c45Rules.Length - c45Rules.IndexOf(":") - 1).Trim();
                    rules.Add(currentRule);
                }else{
                    c45Rules = c45Rules.Substring(c45Rules.IndexOf("Rule "), c45Rules.Length - c45Rules.IndexOf("Rule "));
                    rules.Add(currentRule);
                    endOfConditions = false;
                }

            }

            return rules;
        }


        //filestemNames e filestemData correspondentes, exemplo :
        //filestemNames | filestemData
        //Nausea        | yes
        //urine_pushing | no
        //temperature   | 37
        private string interpretRules(List<Rule> rules, List<string> filestemNames, List<string> filestemData)
        {
            string diagnosis;
            bool satisfyAllConditions;
            List<Rule> possibleRules = new List<Rule>();

            foreach(Rule rule in rules){
                satisfyAllConditions = true;
                foreach(Condition condition in rule.Conditions){
                    foreach(string attribute in filestemNames){
                        if (attribute == condition.Attribute){
                            if (!satisfyCondition(filestemData[filestemNames.IndexOf(attribute)],condition.Operation,condition.Value)){
                                satisfyAllConditions = false;
                            }                            
                        }                        
                    } 
                }
                if (satisfyAllConditions){
                    return rule.Result;
                    //possibleRules.Add(rule);
                }
            }            
            
            //TODO provavelmente inutil esta parte, realizar mais testes com o c4.5
            Rule bestRule = new Rule();
            if(possibleRules.Count == 0){
                diagnosis = this.defaultClass;
                return diagnosis;
            }else{                
                bestRule.Accuracy = 0.0f;
                foreach(Rule rule in possibleRules){
                    if(rule.Accuracy > bestRule.Accuracy){
                        bestRule = rule;
                    }
                }            
            }

            diagnosis = bestRule.Result;

            return diagnosis;
        }

        private bool satisfyCondition(string value, operations operation, string conditionValue){
            switch (operation){
                case operations.equals:
                    if (value == conditionValue){
                        return true;
                    }else{
                        return false;
                    }
                case operations.less:
                    if (float.Parse(value, CultureInfo.InvariantCulture) < float.Parse(conditionValue, CultureInfo.InvariantCulture))
                    {
                        return true;
                    }else{
                        return false;
                    }
                case operations.more:
                    if (float.Parse(value, CultureInfo.InvariantCulture) > float.Parse(conditionValue, CultureInfo.InvariantCulture))
                    {
                        return true;
                    }else{
                        return false;
                    }
                case operations.lessEqual:
                    if (float.Parse(value, CultureInfo.InvariantCulture) <= float.Parse(conditionValue, CultureInfo.InvariantCulture))
                    {
                        return true;
                    }else{
                        return false;
                    }
                case operations.moreEqual:
                    if (float.Parse(value, CultureInfo.InvariantCulture) >= float.Parse(conditionValue, CultureInfo.InvariantCulture))
                    {
                        return true;
                    }else{
                        return false;
                    }
                default:
                    return false;
            }
        }

    }
}
